package com.tbit.uqbike.service.business.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tbit.uqbike.constant.BrandRemoteConfigConstant;
import com.tbit.uqbike.constant.Constants;
import com.tbit.uqbike.constant.RedisConstant;
import com.tbit.uqbike.constant.UserConstants;
import com.tbit.uqbike.manager.exception.BaseException;
import com.tbit.uqbike.object.pojo.Account;
import com.tbit.uqbike.object.pojo.BrandRemoteConfig;
import com.tbit.uqbike.service.base.RedisService;
import com.tbit.uqbike.service.business.BrandRemoteConfigService;
import com.tbit.uqbike.service.business.brand.config.AbstractConfigValidator;
import com.tbit.uqbike.service.business.brand.ctx.BrandConfigContext;
import com.tbit.uqbike.service.business.brand.ctx.ValidateContextHolder;
import com.tbit.uqbike.service.event.eventItem.BrandRemoteConfigEvent;
import com.tbit.uqbike.service.event.EventType;
import com.tbit.uqbike.webmanager.dao.core.AccountConfigExtDao;
import com.tbit.uqbike.webmanager.dao.core.AccountDao;
import com.tbit.uqbike.webmanager.dao.core.BrandRemoteConfigDao;
import com.tbit.uqbike.webmanager.dao.core.SubAccountDao;
import com.tbit.uqbike.webmanager.util.DateUtils;
import com.tbit.uqbike.webmanager.util.StringUtils;
import com.tbit.uqbike.webmanager.util.sign.Md5Utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 品牌对接参数配置Service业务层处理
 *
 * @author jing
 * @date 2021-04-21
 */
@Service
@Slf4j
public class BrandRemoteConfigServiceImpl implements BrandRemoteConfigService {
    @Autowired
    private BrandRemoteConfigDao brandRemoteConfigDao;

    @Autowired
    private SubAccountDao subAccountDao;

    @Autowired
    private List<AbstractConfigValidator> configValidators;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Autowired
    private RedisService redisService;

    @Resource
    private AccountDao accountDao;

    @Resource
    private AccountConfigExtDao accountConfigExtDao;

    /**
     * 查询品牌对接参数配置列表
     *
     * @param brandRemoteConfig 品牌对接参数配置
     * @return 品牌对接参数配置
     */
    @Override
    public PageInfo<BrandRemoteConfig> selectBrandRemoteConfigList(BrandRemoteConfig brandRemoteConfig) {
        PageInfo<BrandRemoteConfig> pageInfo;
        // 都会查一次数据库，区别是是否指定分页
        if (brandRemoteConfig.getPageNO() != null && brandRemoteConfig.getRowCount() != null) {
            pageInfo = PageHelper.startPage(brandRemoteConfig.getPageNO().intValue(), brandRemoteConfig.getRowCount().intValue())
                    .doSelectPageInfo(() -> brandRemoteConfigDao.selectBrandRemoteConfigList(brandRemoteConfig));
        } else {
            pageInfo = new PageInfo<>(brandRemoteConfigDao.selectBrandRemoteConfigList(brandRemoteConfig));
        }

        List<BrandRemoteConfig> brandRemoteConfigs = pageInfo.getList();
        List<Integer> configList = new ArrayList<>();
        if (brandRemoteConfigs != null) {
            configList = brandRemoteConfigs.stream().map(BrandRemoteConfig::getConfigType).collect(Collectors.toList());
        }
        /*
         * 分页参数空，则填充默认值
         */
        if (brandRemoteConfig.getPageNO() == null && brandRemoteConfig.getRowCount() == null) {
            // 上面不管有没有分页参数其实都做了数据库查询，只有在数据库中的key没有value的时候才会填充写死的默认值
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.REAL_NAME_COUNT, "5", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.PC_FREE_LOGIN, "freeLoginMode=0&effectiveDay=10080", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.MAIN_FREE_LOGIN, "freeLoginMode=0&effectiveDay=10080", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.NO_LOGIN_EXPIRY_DAYS, BrandRemoteConfigConstant.DEFAULT_NO_LOGIN_EXPIRY_DAYS_VALUE_MINUTE.toString(), brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            // 服务商支付开关，默认关闭
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.WX_MCH_PAY_SWITCH, "0", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.CLIENT_MANUAL_INPUT, "1", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.SERVICE_SWITCH, "serviceSwitch=1", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.DISABLE_VERIFY_OUTSIDE, "disableVerifyOutsideOps=0", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
            /**/
            completeBrandRemoteConfig(configList, BrandRemoteConfigConstant.BLOCK_VIRTUAL_PHONE_SWITCH, "0", brandRemoteConfig.getAccountId(), brandRemoteConfigs);
        }
        return pageInfo;
    }

    /**
     * 补全默认配置
     *
     * @param configList               数据库已存在类型
     * @param type               当前需要判断补全的类型
     * @param paramKV            默认值
     * @param accountId          品牌id
     * @param brandRemoteConfigs
     */
    private void completeBrandRemoteConfig(List<Integer> configList, Integer type, String paramKV, Integer accountId, List<BrandRemoteConfig> brandRemoteConfigs) {
        if (!configList.contains(type)) {
            brandRemoteConfigs.add(new BrandRemoteConfig(accountId, type, paramKV));
        }
    }

    @Override
    public String checkConfigTypeUnique(BrandRemoteConfig brandRemoteConfig) {
        return StringUtils.isNotEmpty(brandRemoteConfigDao.selectBrandRemoteConfigList(brandRemoteConfig)) ? UserConstants.NOT_UNIQUE : UserConstants.UNIQUE;
    }

    /**
     * 新增品牌对接参数配置
     *
     * @param brandRemoteConfig 品牌对接参数配置
     * @return 结果
     */
    @Override
    public int insertBrandRemoteConfig(BrandRemoteConfig brandRemoteConfig) {
        brandRemoteConfig.setUpdateTime(DateUtils.getNowDate());
        return brandRemoteConfigDao.insertBrandRemoteConfig(brandRemoteConfig);
    }

    @Override
    //@Transactional(rollbackFor=Exception.class)
    public int addBrandRemoteConfig(List<BrandRemoteConfig> brandRemoteConfigList) {
        if (CollUtil.isEmpty(brandRemoteConfigList)) {
            return 0;
        }
        Integer accountId = brandRemoteConfigList.get(0).getAccountId();
        List<Integer> selectionType = brandRemoteConfigList.stream()
                .map(BrandRemoteConfig::getConfigType)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        QueryWrapper<BrandRemoteConfig> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("accountId", "configType", "paramKV", "updateTime", "remark")
                .eq(Objects.nonNull(accountId), "accountId", accountId)
                .in(CollectionUtil.isNotEmpty(selectionType), "configType", selectionType);

        List<BrandRemoteConfig> selectionList = Optional.ofNullable(brandRemoteConfigDao.selectList(queryWrapper))
                .orElse(new ArrayList<>());
        List<BrandRemoteConfig> addOrUpdateList = brandRemoteConfigList
                .stream()
                //重写了equals方法 和 hashcode方法
                .filter(x -> !selectionList.contains(x))
                .collect(Collectors.toList());

        List<BrandRemoteConfig> deleteList = new ArrayList<>();

        for (BrandRemoteConfig brandRemoteConfig : brandRemoteConfigList) {
            if (Objects.equals(brandRemoteConfig.getConfigType(), BrandRemoteConfigConstant.SERVICE_SWITCH) && "serviceSwitch=1".equals(brandRemoteConfig.getParamKV())){
                //删除停运配置
            }
        }


        /**
         * 删除配置
         * selectionList改成了 只加载入参部分之后，这里事实上已经不会有需要删除的数据了
         * 先留着
         */
        selectionList.stream()
                .forEach(x -> {
                    boolean delete = brandRemoteConfigList.stream()
                            .noneMatch(y -> x.getConfigType().equals(y.getConfigType()) && x.getAccountId().equals(y.getAccountId()));
                    if (delete) {
                        deleteList.add(x);
                    }
                });

        if (CollUtil.isEmpty(addOrUpdateList) && CollUtil.isEmpty(deleteList)) {
            return 0;
        }

        /*
         * 校验配置
         * 每个配置可单独实现校验逻辑
         */
        BrandConfigContext ctx = ValidateContextHolder.get();
        addOrUpdateList.forEach(config -> ctx.configMap.put(config.getConfigType(), config));
        if (CollectionUtil.isNotEmpty(addOrUpdateList) && CollectionUtil.isNotEmpty(configValidators)) {
            addOrUpdateList.stream().filter(Objects::nonNull).forEach(brandRemoteConfig -> {
                configValidators.forEach(validator -> validator.valid(brandRemoteConfig));
            });
        }
        ValidateContextHolder.remove();

        List<BrandRemoteConfig> addList = new ArrayList<>();
        List<BrandRemoteConfig> updateList = new ArrayList<>();
        addOrUpdateList.forEach(brandRemoteConfig -> brandRemoteConfig.setUpdateTime(DateUtils.getNowDate()));
        separateAddOrUpdate(selectionList, addOrUpdateList, addList, updateList);


        int delete = 0;
        if (CollUtil.isNotEmpty(deleteList)) {
            delete = brandRemoteConfigDao.deleteInBatches(deleteList);
            for (BrandRemoteConfig brandRemoteConfig : deleteList) {
                redisService.del(RedisConstant.BRANDREMOTECONFIG_KEY_PREFIX + brandRemoteConfig.getAccountId());
            }
            publishEvent(deleteList, EventType.DELETE);
        }

        int add = 0;
        if (CollUtil.isNotEmpty(addList)) {
            add = brandRemoteConfigDao.addBrandRemoteConfig(addList);
            publishEvent(addList, EventType.ADD);
        }

        AtomicInteger update = new AtomicInteger();
        if (CollUtil.isNotEmpty(updateList)) {
            updateList.forEach(brandRemoteConfig -> {
                int res = brandRemoteConfigDao.updateBrandRemoteConfig(brandRemoteConfig);
                update.addAndGet(res);
                redisService.del(RedisConstant.BRANDREMOTECONFIG_KEY_PREFIX + brandRemoteConfig.getAccountId());
            });
            publishEvent(updateList, EventType.UPDATE);
        }
        return delete + add + update.get();
    }


    private void separateAddOrUpdate(List<BrandRemoteConfig> all, List<BrandRemoteConfig> addOrUpdateList, List<BrandRemoteConfig> addList, List<BrandRemoteConfig> updateList) {
        addOrUpdateList.forEach(brandRemoteConfig -> {
            boolean exist = all.stream()
                    .anyMatch(x -> x.getAccountId().equals(brandRemoteConfig.getAccountId())
                            && x.getConfigType().equals(brandRemoteConfig.getConfigType())
                            && !x.getParamKV().equals(brandRemoteConfig.getParamKV())
                    );
            if (exist) {
                updateList.add(brandRemoteConfig);
            } else {
                boolean add = all.stream()
                        .noneMatch(x -> x.getAccountId().equals(brandRemoteConfig.getAccountId())
                                && x.getConfigType().equals(brandRemoteConfig.getConfigType())
                        );
                if (add) {
                    addList.add(brandRemoteConfig);
                }
            }
        });
    }


    private void publishEvent(List<BrandRemoteConfig> brandRemoteConfigs, EventType eventType) {
        if (CollUtil.isEmpty(brandRemoteConfigs)) {
            return;
        }
        BrandRemoteConfigEvent event = new BrandRemoteConfigEvent(brandRemoteConfigs, eventType);
        eventPublisher.publishEvent(event);
    }


    /**
     * 修改品牌对接参数配置
     *
     * @param brandRemoteConfig 品牌对接参数配置
     * @return 结果
     */
    @Override
    public int updateBrandRemoteConfig(BrandRemoteConfig brandRemoteConfig) {
        brandRemoteConfig.setUpdateTime(DateUtils.getNowDate());
        int res = brandRemoteConfigDao.updateBrandRemoteConfig(brandRemoteConfig);
        redisService.del(RedisConstant.BRANDREMOTECONFIG_KEY_PREFIX + brandRemoteConfig.getAccountId());
        return res;
    }


    /**
     * 删除品牌对接参数配置信息
     *
     * @param brandRemoteConfig 品牌对接参数配置
     * @return 结果
     */
    @Override
    public int deleteBrandRemoteConfigById(BrandRemoteConfig brandRemoteConfig) {
        //校验是否删除tbit优驱API配置
        if (BrandRemoteConfigConstant.TBIT_CONFIG.equals(brandRemoteConfig.getConfigType())){
            updateAllAccountNameAuthType(brandRemoteConfig.getAccountId());
        }
        int res = brandRemoteConfigDao.deleteBrandRemoteConfigById(brandRemoteConfig);
        redisService.del(RedisConstant.BRANDREMOTECONFIG_KEY_PREFIX + brandRemoteConfig.getAccountId());
        return res;
    }

    public BrandRemoteConfig getRemoteConfigByBrandIdAndConfigType(Integer brandId, Integer configType) {
        return brandRemoteConfigDao.getBrandRemoteConfig(brandId, configType);
    }

    @Override
    public String getTbitConfig(Integer brandId) {
        BrandRemoteConfig brandRemoteConfig = brandRemoteConfigDao.getBrandRemoteConfig(brandId, BrandRemoteConfigConstant.TBIT_CONFIG);
        if (brandRemoteConfig != null) {
            String[] strings = brandRemoteConfig.getParamKV().split("&");
            if (strings.length < 3) {
                throw new BaseException("brand.function.is.not.exist");
            }
            return "http://openapiweb.uqbike.com/exorder?i=" + strings[0].substring(strings[0].indexOf("=") + 1)
                    + "&s=" + Md5Utils.hash(strings[2].substring(strings[2].indexOf("=") + 1));
        } else {
            throw new BaseException("brand.function.is.not.exist");
        }
    }

    @Override
    public void removeCache(Integer brandId, Integer configType) {
        log.info("删除品牌对接参数配置缓存：brandId:{},configType:{}", brandId, configType);
        String key = RedisConstant.BRANDREMOTECONFIG_KEY_PREFIX + brandId.toString() + "." + configType.toString();
        redisService.del(key);

    }


    /**
     * 修改对应品牌下所有区域的实名认证类型
     * @param brandId 品牌id
     */
    private void updateAllAccountNameAuthType(Integer brandId) {
        //获取品牌下所有区域
        List<Account> accountList = accountDao.getById(brandId);
        List<Integer> accountIdList = accountList.stream().map(Account::getAccountId).collect(Collectors.toList());
        //修改所有区域的实名认证类型
        accountConfigExtDao.batchUpdate(accountIdList, "nameAuthType", 0);
        for (Integer accountId : accountIdList) {
            redisService.del(Constants.ACCOUNTCONFIG_KEY_PREFIX + accountId);
        }
        log.info("删除品牌配置tbit本地信息，该品牌下所有区域实名认证类型默认修改为0，修改区域涉及：{}", accountIdList);
    }



}
